/**
 * @author zhouheng
 * @date 2022年03月05日 15:52
 */

package com.gulimall.search.gulimallsearch.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.SkuEsModel;
import com.gulimall.search.gulimallsearch.config.GuliEsConfig;
import com.gulimall.search.gulimallsearch.constant.EsConstant;
import com.gulimall.search.gulimallsearch.feign.ProductFeignService;
import com.gulimall.search.gulimallsearch.service.SearchService;
import com.gulimall.search.gulimallsearch.vo.AttrResponseVo;
import com.gulimall.search.gulimallsearch.vo.SearchParam;
import com.gulimall.search.gulimallsearch.vo.SearchResult;
import com.gulimall.search.gulimallsearch.vo.SearchResult.AttrVo;
import com.gulimall.search.gulimallsearch.vo.SearchResult.BrandVo;
import com.gulimall.search.gulimallsearch.vo.SearchResult.CatalogVo;
import com.sun.deploy.net.URLEncoder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    RestHighLevelClient client;

    @Autowired
    ProductFeignService productFeignService;

    @Override
    public SearchResult search(SearchParam searchParam) {
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        SearchResult searchResult = null;

        try {
            SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchResponse searchResponse = client.search(searchRequest, GuliEsConfig.COMMON_OPTIONS);

            // 3.分析响应数据
            searchResult = buildSearchResult(searchResponse, searchParam);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return searchResult;
    }

    /*
     * @author zhouheng
     * 处理结果
     * @date 2022/3/6 0006 23:29
     * @param null 
     * @return null
     */
    private SearchResult buildSearchResult(SearchResponse searchResponse, SearchParam searchParam) {
        SearchResult searchResult = new SearchResult();
        List<SkuEsModel> skuEsModels = new ArrayList<>();
        SearchHits hits = searchResponse.getHits();
        // 1 获取所有的商品
        if (hits.getHits() != null && hits.getHits().length > 0) {
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);

                if (StringUtils.isNotBlank(searchParam.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String hignlightField = skuTitle.getFragments()[0].toString();
                    skuEsModel.setSkuTitle(hignlightField);
                }

                /** * 查询到的所有商品信息*/
                skuEsModels.add(skuEsModel);
            }
        }
        searchResult.setProducts(skuEsModels);

        // 2 当前所有商品设计到的所有属性信息
        ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        List<? extends Bucket> attrIdAggBuckets = attr_id_agg.getBuckets();
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        for (Bucket attrIdAggBucket : attrIdAggBuckets) {
            long attrId = attrIdAggBucket.getKeyAsNumber().longValue();
            AttrVo attrVo = new AttrVo();
            attrVo.setAttrId(attrId);

            ParsedStringTerms attr_name_agg = attrIdAggBucket.getAggregations().get("attr_name_agg");
            String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
            ParsedStringTerms attr_value_agg = attrIdAggBucket.getAggregations().get("attr_value_agg");
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attr_value_agg.getBuckets().stream().map(t -> {
                return t.getKeyAsString();
            }).collect(Collectors.toList()));

            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);

        // 3 当前商品涉及到的所有品牌信息
        Aggregations aggregations = searchResponse.getAggregations();
        ParsedLongTerms parsedLongTerms = aggregations.get("brandIdAgg");
        List<? extends Terms.Bucket> buckets = parsedLongTerms.getBuckets();
        List<BrandVo> brandVos = new ArrayList<>();
        for (Bucket bucket : buckets) {
            Number keyAsNumber = bucket.getKeyAsNumber();
            BrandVo brandVo = new BrandVo();
            brandVo.setBrandId(keyAsNumber.longValue());

            ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
            brandVo.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
            ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
            if (brand_img_agg.getBuckets() != null && brand_img_agg.getBuckets().size() > 0) {
                brandVo.setBrandName(brand_img_agg.getBuckets().get(0).getKeyAsString());
            }

            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);
        // 4 当前商品涉及到的所有分类信息
        ParsedLongTerms catelogAgg = aggregations.get("catelogAgg");
        List<? extends Terms.Bucket> catelogAggBuckets = catelogAgg.getBuckets();
        List<CatalogVo> catalogVos = new ArrayList<>();
        for (Bucket bucket : catelogAggBuckets) {
            Number keyAsNumber = bucket.getKeyAsNumber();
            CatalogVo catalogVo = new CatalogVo();
            catalogVo.setCatalogId(keyAsNumber.longValue());

            ParsedStringTerms catelogNameAgg = bucket.getAggregations().get("catelog_name_agg");
            catalogVo.setCatalogName(catelogNameAgg.getBuckets().get(0).getKeyAsString());
            catalogVos.add(catalogVo);
        }
        searchResult.setCatalogs(catalogVos);
        // 以上是聚合信息中得到

        // 5 分页信息-页码 总记录数 总页码数
        // 当前页码
        searchResult.setPageNum(searchParam.getPageNum());
        // 总记录数
        long totalRecord = hits.getTotalHits().value;
        searchResult.setTotal(totalRecord);
        // 总页码
        Long totalPageNum = totalRecord % EsConstant.PRODUCT_PASIZE > 0 ?
            totalRecord / EsConstant.PRODUCT_PASIZE + 1
            : totalRecord / EsConstant.PRODUCT_PASIZE;
        searchResult.setTotalPages(totalPageNum.intValue());

        // 分页导航
        ArrayList<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPageNum; i++){
            pageNavs.add(i);
        }
        searchResult.setPageNavs(pageNavs);

        // 面包屑导航

        // 6.构建面包屑导航功能
        if(searchParam.getAttrs() != null){
            List<SearchResult.NavVo> navVos = searchParam.getAttrs().stream().map(attr -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] s = attr.split("_");
                navVo.setNavValue(s[1]);
                R r = productFeignService.getAttrsInfo(Long.parseLong(s[0]));
                // 将已选择的请求参数添加进去 前端页面进行排除
                searchResult.getAttrIds().add(Long.parseLong(s[0]));
                if(r.getCode() == 0){
                    AttrResponseVo data = r.getData(new TypeReference<AttrResponseVo>(){});
                    navVo.setName(data.getAttrName());
                }else{
                    // 失败了就拿id作为名字
                    navVo.setName(s[0]);
                }
                // 拿到所有查询条件 替换查询条件
                String replace = replaceQueryString(searchParam, attr, "attrs");
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                return navVo;
            }).collect(Collectors.toList());
            searchResult.setNavs(navVos);
        }

        // 品牌、分类
        if(searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0){
            List<SearchResult.NavVo> navs = searchResult.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setName("品牌");
            // TODO 远程查询所有品牌
            R r = productFeignService.brandInfo(searchParam.getBrandId());
            if(r.getCode() == 0){
                List<BrandVo> brand = r.getData("data", new TypeReference<List<BrandVo>>() {});
                StringBuffer buffer = new StringBuffer();
                // 替换所有品牌ID
                String replace = "";
                for (BrandVo brandVo : brand) {
                    buffer.append(brandVo.getBrandName() + ";");
                    replace = replaceQueryString(searchParam, brandVo.getBrandId() + "", "brandId");
                }
                navVo.setNavValue(buffer.toString());
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
            }
            navs.add(navVo);
        }



        return searchResult;
    }

    private String replaceQueryString(SearchParam Param, String value, String key) {
        String encode = null;
        try {
            encode = URLEncoder.encode(value,"UTF-8");
            // 浏览器对空格的编码和java的不一样
            encode = encode.replace("+","%20");
            encode = encode.replace("%28", "(").replace("%29",")");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return Param.get_queryString().replace("&" + key + "=" + encode, "");
    }

    /*
     * @author zhouheng
     * @date 2022/3/6 0006 23:27
     * @param null 
     * @return null
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        // 创建查询请求对象，将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("product");

        // 构建查询条件（注意：termsQuery 支持多种格式查询，如 boolean、int、double、string 等，这里使用的是 string 的查询）
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //构建bool -query
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // must 模糊匹配
        if (StringUtils.isNotBlank(searchParam.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
        }
        // catelogId 精确匹配
        if (searchParam.getCatalog3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
        }
        // 是否有库存 hasStock
        if (searchParam.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }
        // 品牌id brandId
        if (!CollectionUtils.isEmpty(searchParam.getBrandId())) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandId()));
        }
        // 价格区间 array
//        1_500  _500 500_
        if (StringUtils.isNotBlank(searchParam.getSkuPrice())) {
            String[] split = searchParam.getSkuPrice().split("_");
            if (split != null && split.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(split[0]).lte(split[1]));
            } else if (split.length == 1) {
                if (searchParam.getSkuPrice().startsWith("_")) {
                    boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").lte(split[0]));
                } else {
                    boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(split[0]));
                }
            }
        }
        // 属性id 前提nested
        // 1_5g&2_128G
        if (!CollectionUtils.isEmpty(searchParam.getAttrs())) {
            for (String attr : searchParam.getAttrs()) {
                String[] split = attr.split("_");
                String key = split[0];
                String value = split[1];
                // 内嵌查詢
                BoolQueryBuilder boolNestedQueryBuilder = QueryBuilders.boolQuery();
                boolNestedQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId", key));
                boolNestedQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",value));
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", boolNestedQueryBuilder, ScoreMode.None));
            }
        }

        // 把以前所有条件都拿来进行封装
        searchSourceBuilder.query(boolQueryBuilder);

        // 排序 分页 高亮
        if (StringUtils.isNotBlank(searchParam.getSort())) {
            String[] split = searchParam.getSort().split("_");
            String field = split[0];
            String sortType = split[1];
            searchSourceBuilder.sort(field, StringUtils.equalsAnyIgnoreCase(SortOrder.ASC.toString(), sortType) ? SortOrder.ASC: SortOrder.DESC);

            // pageSize = 2
            // pageNum = 2, from = pageSzie * (pageNum - 1) +1, to = from + pageSize
            searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.PRODUCT_PASIZE);
            searchSourceBuilder.size(EsConstant.PRODUCT_PASIZE);

            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
       }

        // 品牌聚合
        TermsAggregationBuilder brandIdAgg = AggregationBuilders.terms("brandIdAgg");
        brandIdAgg.field("brandId").size(50);
        // 品牌名称 子聚合
        TermsAggregationBuilder brand_name_agg = AggregationBuilders.terms("brand_name_agg");
        brand_name_agg.field("brandName").size(1);
        brandIdAgg.subAggregation(brand_name_agg);
        // 品牌图片 子聚合
        TermsAggregationBuilder brand_img_agg = AggregationBuilders.terms("brand_img_agg");
        brand_img_agg.field("brandImg").size(1);
        brandIdAgg.subAggregation(brand_img_agg);
        searchSourceBuilder.aggregation(brandIdAgg);

        // 分类聚合
        TermsAggregationBuilder catelogAgg = AggregationBuilders.terms("catelogAgg");
        catelogAgg.field("catalogId").size(20);
        // 分类名称 子聚合
        catelogAgg.subAggregation(AggregationBuilders.terms("catelog_name_agg").field("catalogName").size(1));
        searchSourceBuilder.aggregation(catelogAgg);

        // 属性聚合
        NestedAggregationBuilder nested = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(20));
        nested.subAggregation(attr_id_agg);

        searchSourceBuilder.aggregation(nested);

        System.out.println(searchSourceBuilder.toString());

        searchRequest.source(searchSourceBuilder);
        
        return searchRequest;
    }
}